package com.bah.tract.client;

import java.awt.Component;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.MouseListener;
import java.io.FileNotFoundException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.prefs.Preferences;

import com.bah.tract.client.cache.CachingProjectService;
import com.bah.tract.client.cache.CachingWorkService;
import com.bah.tract.client.cache.ObjectCache;
import com.bah.tract.client.cache.Transactional;
import com.bah.tract.client.cache.je.ClientData;
import com.bah.tract.client.cache.je.DateKeyCreator;
import com.bah.tract.client.cache.je.ProjectCache;
import com.bah.tract.client.cache.je.TransactionalInterceptor;
import com.bah.tract.client.cache.je.UnpostedWorkCache;
import com.bah.tract.client.cache.je.WorkCache;
import com.bah.tract.client.cache.je.WorkCacheIndex;
import com.bah.tract.client.cache.je.WorkStoreCache;
import com.bah.tract.client.ui.DataDialog;
import com.bah.tract.client.ui.IconMouseListener;
import com.bah.tract.client.ui.JiraPreferencePanel;
import com.bah.tract.client.ui.JiraSubmitDialog;
import com.bah.tract.client.ui.PreferencePanel;
import com.bah.tract.client.ui.ProjectSubmitDialog;
import com.bah.tract.client.ui.TOLPreferencePanel;
import com.bah.tract.client.ui.TOLSubmitDialog;
import com.bah.tract.client.ui.TRACTTrayIcon;
import com.bah.tract.client.ui.TaskEntryForm;
import com.bah.tract.client.ui.TimeCardDialog;
import com.bah.tract.client.ui.TimeCardMouseListener;
import com.bah.tract.client.ui.menu.PreferencesMenuItem;
import com.bah.tract.client.ui.menu.SubmitTimeMenuItem;
import com.bah.tract.client.ui.menu.TRACTMenu;
import com.bah.tract.client.ui.menu.TaskReminderMenuItem;
import com.bah.tract.client.ui.menu.TimeCardMenuItem;
import com.bah.tract.client.workstore.tol.TOLClient;
import com.bah.tract.client.workstore.tol.TOLClientImpl;
import com.bah.tract.client.workstore.tol.TOLTaskProvider;
import com.bah.tract.client.workstore.tol.TaxLocationProvider;
import com.bah.tract.security.PasswordEncryptor;
import com.bah.tract.security.X509PasswordEncryptor;
import com.bah.tract.service.Project;
import com.bah.tract.service.ProjectService;
import com.bah.tract.service.ProjectService_Service;
import com.bah.tract.service.Work;
import com.bah.tract.service.WorkService;
import com.bah.tract.service.WorkService_Service;
import com.bah.tract.service.WorkStoreList;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.Matchers;
import com.google.inject.name.Names;
import com.sleepycat.bind.serial.ClassCatalog;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;

public class ClientModule extends AbstractModule {

	protected void configureServices() {
		// Create server implementations
		final WorkService workService = new WorkService_Service(getClass()
				.getResource("/wsdl/work-service.wsdl")).getWorkService();
		bind(WorkService.class).annotatedWith(Names.named("server"))
				.toInstance(workService);

		final ProjectService projectService = new ProjectService_Service(
				getClass().getResource("/wsdl/project-service.wsdl"))
				.getProjectService();
		bind(ProjectService.class).annotatedWith(Names.named("server"))
				.toInstance(projectService);
	}

	@Override
	protected final void configure() {
		try {
			configureServices();

			bind(Preferences.class).toInstance(
					Preferences.userNodeForPackage(Main.class));

			// Create Databases
			final ClientData clientData = new ClientData(
					"C:/Users/524506/tract/data");

			// setup the caches
			bind(new TypeLiteral<ObjectCache<Long, Project>>() {
			}).to(ProjectCache.class);
			bind(new TypeLiteral<ObjectCache<Long, WorkStoreList>>() {
			}).to(WorkStoreCache.class);
			bind(new TypeLiteral<ObjectCache<String, Long>>() {
			}).to(UnpostedWorkCache.class);
			bind(new TypeLiteral<ObjectCache<String, Work>>() {
			}).to(WorkCache.class);
			bind(Database.class).annotatedWith(Names.named("workStoreDb"))
					.toInstance(clientData.openDatabase("work_store_store"));
			final Database workDb = clientData.openDatabase("work_store");
			bind(Database.class).annotatedWith(Names.named("workDb"))
					.toInstance(workDb);
			bind(Database.class).annotatedWith(Names.named("unpostedDb"))
					.toInstance(clientData.openDatabase("unposted_store"));
			bind(Database.class).annotatedWith(Names.named("projectDb"))
					.toInstance(clientData.openDatabase("project_store"));
			final DateKeyCreator dateKeyCreator = new DateKeyCreator(
					clientData.getJavaCatalog());
			bind(Database.class).annotatedWith(Names.named("dateDb"))
					.toInstance(
							clientData.openSecondaryDatabase("date_store",
									workDb, dateKeyCreator));
			bind(Environment.class).toInstance(clientData.getEnv());
			bind(ClassCatalog.class).toInstance(clientData.getJavaCatalog());

			// wrap the server with local caches
			bind(ProjectService.class).to(CachingProjectService.class);
			bind(WorkService.class).to(CachingWorkService.class);

			// Create single service for background operations
			bind(ScheduledExecutorService.class).toInstance(
					Executors.newScheduledThreadPool(1));

			// Make sure all methods annotated with Transactional annotations
			// are called in a transaction
			final TransactionalInterceptor interceptor = new TransactionalInterceptor(
					clientData.getEnv());
			requestInjection(interceptor);
			bindInterceptor(Matchers.any(),
					Matchers.annotatedWith(Transactional.class), interceptor);

			bind(Timer.class).to(TRACTTrayIcon.class);
			bind(TrayIcon.class).to(TRACTTrayIcon.class);

			// WorkCache does double duty
			bind(WorkLog.class).to(WorkCacheIndex.class);

			final Image image = Toolkit.getDefaultToolkit().getImage(
					"stopwatch.PNG");
			bind(Image.class).annotatedWith(Names.named("defaultImage"))
					.toInstance(image);
			final Image playImage = Toolkit.getDefaultToolkit().getImage(
					"stopwatch-play.PNG");
			bind(Image.class).annotatedWith(Names.named("playImage"))
					.toInstance(playImage);
			final Image pauseImage = Toolkit.getDefaultToolkit().getImage(
					"stopwatch-pause.PNG");
			bind(Image.class).annotatedWith(Names.named("pauseImage"))
					.toInstance(pauseImage);

			bind(PreferencePanel.class).annotatedWith(
					Names.named("tolPreferences")).to(TOLPreferencePanel.class);
			bind(PreferencePanel.class).annotatedWith(
					Names.named("jiraPreferences")).to(
					JiraPreferencePanel.class);

			bind(MouseListener.class).to(IconMouseListener.class);
			bind(MouseListener.class).annotatedWith(
					Names.named("timeCardListener")).to(
					TimeCardMouseListener.class);
			bind(Component.class).annotatedWith(Names.named("taskSelector"))
					.to(TaskEntryForm.class);

			bind(PasswordEncryptor.class).to(X509PasswordEncryptor.class);
			bind(TOLClient.class).to(TOLClientImpl.class);
			// bind(TOLClient.class).to(MockTOLClient.class);
			bind(TaxLocationProvider.class).to(TOLTaskProvider.class);

			bind(PopupMenu.class).to(TRACTMenu.class);
			bind(MenuItem.class).annotatedWith(Names.named("timeCardItem")).to(
					TimeCardMenuItem.class);
			bind(MenuItem.class).annotatedWith(Names.named("reminderItem")).to(
					TaskReminderMenuItem.class);
			bind(MenuItem.class).annotatedWith(Names.named("preferencesItem"))
					.to(PreferencesMenuItem.class);
			bind(MenuItem.class).annotatedWith(Names.named("submitItem")).to(
					SubmitTimeMenuItem.class);
			bind(Component.class).annotatedWith(Names.named("timeCard")).to(
					TimeCardDialog.class);
			bind(Component.class).annotatedWith(Names.named("tolDialog")).to(
					TOLSubmitDialog.class);
			bind(Component.class).annotatedWith(Names.named("jiraDialog")).to(
					JiraSubmitDialog.class);
			bind(Component.class).annotatedWith(Names.named("projectDialog"))
					.to(ProjectSubmitDialog.class);
			bind(DataDialog.class).to(TimeCardDialog.class);

		} catch (final DatabaseException e) {
			throw new RuntimeException(e);
		} catch (final FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
}
